/*
 * Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
/*
 * Copyright 2001-2005 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.sun.org.apache.xerces.internal.parsers;

import java.util.Locale;
import java.util.Stack;

import com.sun.org.apache.xerces.internal.dom.AttrImpl;
import com.sun.org.apache.xerces.internal.dom.CoreDocumentImpl;
import com.sun.org.apache.xerces.internal.dom.DOMErrorImpl;
import com.sun.org.apache.xerces.internal.dom.DOMMessageFormatter;
import com.sun.org.apache.xerces.internal.dom.DeferredDocumentImpl;
import com.sun.org.apache.xerces.internal.dom.DocumentImpl;
import com.sun.org.apache.xerces.internal.dom.DocumentTypeImpl;
import com.sun.org.apache.xerces.internal.dom.ElementDefinitionImpl;
import com.sun.org.apache.xerces.internal.dom.ElementImpl;
import com.sun.org.apache.xerces.internal.dom.ElementNSImpl;
import com.sun.org.apache.xerces.internal.dom.EntityImpl;
import com.sun.org.apache.xerces.internal.dom.EntityReferenceImpl;
import com.sun.org.apache.xerces.internal.dom.NodeImpl;
import com.sun.org.apache.xerces.internal.dom.NotationImpl;
import com.sun.org.apache.xerces.internal.dom.PSVIAttrNSImpl;
import com.sun.org.apache.xerces.internal.dom.PSVIDocumentImpl;
import com.sun.org.apache.xerces.internal.dom.PSVIElementNSImpl;
import com.sun.org.apache.xerces.internal.dom.TextImpl;
import com.sun.org.apache.xerces.internal.impl.Constants;
import com.sun.org.apache.xerces.internal.impl.dv.XSSimpleType;
import com.sun.org.apache.xerces.internal.util.DOMErrorHandlerWrapper;
import com.sun.org.apache.xerces.internal.xni.Augmentations;
import com.sun.org.apache.xerces.internal.xni.NamespaceContext;
import com.sun.org.apache.xerces.internal.xni.QName;
import com.sun.org.apache.xerces.internal.xni.XMLAttributes;
import com.sun.org.apache.xerces.internal.xni.XMLLocator;
import com.sun.org.apache.xerces.internal.xni.XMLResourceIdentifier;
import com.sun.org.apache.xerces.internal.xni.XMLString;
import com.sun.org.apache.xerces.internal.xni.XNIException;
import com.sun.org.apache.xerces.internal.xni.parser.XMLParserConfiguration;
import com.sun.org.apache.xerces.internal.xs.AttributePSVI;
import com.sun.org.apache.xerces.internal.xs.ElementPSVI;
import com.sun.org.apache.xerces.internal.xs.XSTypeDefinition;
import com.sun.org.apache.xerces.internal.utils.ObjectFactory;
import org.w3c.dom.Attr;
import org.w3c.dom.CDATASection;
import org.w3c.dom.Comment;
import org.w3c.dom.DOMError;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Element;
import org.w3c.dom.EntityReference;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.ProcessingInstruction;
import org.w3c.dom.Text;
import org.w3c.dom.ls.LSParserFilter;
import org.w3c.dom.traversal.NodeFilter;
import org.xml.sax.SAXException;

/**
 * This is the base class of all DOM parsers. It implements the XNI
 * callback methods to create the DOM tree. After a successful parse of
 * an XML document, the DOM Document object can be queried using the
 * <code>getDocument</code> method. The actual pipeline is defined in
 * parser configuration.
 *
 * @author Arnaud Le Hors, IBM
 * @author Andy Clark, IBM
 * @author Elena Litani, IBM
 * @version $Id: AbstractDOMParser.java,v 1.10 2010-11-01 04:40:09 joehw Exp $
 */
public class AbstractDOMParser extends AbstractXMLDocumentParser {

  //
  // Constants
  //

  // feature ids

  /**
   * Feature id: namespace.
   */
  protected static final String NAMESPACES =
      Constants.SAX_FEATURE_PREFIX + Constants.NAMESPACES_FEATURE;

  /**
   * Feature id: create entity ref nodes.
   */
  protected static final String CREATE_ENTITY_REF_NODES =
      Constants.XERCES_FEATURE_PREFIX + Constants.CREATE_ENTITY_REF_NODES_FEATURE;

  /**
   * Feature id: include comments.
   */
  protected static final String INCLUDE_COMMENTS_FEATURE =
      Constants.XERCES_FEATURE_PREFIX + Constants.INCLUDE_COMMENTS_FEATURE;

  /**
   * Feature id: create cdata nodes.
   */
  protected static final String CREATE_CDATA_NODES_FEATURE =
      Constants.XERCES_FEATURE_PREFIX + Constants.CREATE_CDATA_NODES_FEATURE;

  /**
   * Feature id: include ignorable whitespace.
   */
  protected static final String INCLUDE_IGNORABLE_WHITESPACE =
      Constants.XERCES_FEATURE_PREFIX + Constants.INCLUDE_IGNORABLE_WHITESPACE;

  /**
   * Feature id: defer node expansion.
   */
  protected static final String DEFER_NODE_EXPANSION =
      Constants.XERCES_FEATURE_PREFIX + Constants.DEFER_NODE_EXPANSION_FEATURE;


  /**
   * Recognized features.
   */
  private static final String[] RECOGNIZED_FEATURES = {
      NAMESPACES,
      CREATE_ENTITY_REF_NODES,
      INCLUDE_COMMENTS_FEATURE,
      CREATE_CDATA_NODES_FEATURE,
      INCLUDE_IGNORABLE_WHITESPACE,
      DEFER_NODE_EXPANSION
  };

  // property ids

  /**
   * Property id: document class name.
   */
  protected static final String DOCUMENT_CLASS_NAME =
      Constants.XERCES_PROPERTY_PREFIX + Constants.DOCUMENT_CLASS_NAME_PROPERTY;

  protected static final String CURRENT_ELEMENT_NODE =
      Constants.XERCES_PROPERTY_PREFIX + Constants.CURRENT_ELEMENT_NODE_PROPERTY;

  // protected static final String GRAMMAR_POOL =
  // Constants.XERCES_PROPERTY_PREFIX + Constants.XMLGRAMMAR_POOL_PROPERTY;

  /**
   * Recognized properties.
   */
  private static final String[] RECOGNIZED_PROPERTIES = {
      DOCUMENT_CLASS_NAME,
      CURRENT_ELEMENT_NODE,
  };

  // other

  /**
   * Default document class name.
   */
  protected static final String DEFAULT_DOCUMENT_CLASS_NAME =
      "com.sun.org.apache.xerces.internal.dom.DocumentImpl";

  protected static final String CORE_DOCUMENT_CLASS_NAME =
      "com.sun.org.apache.xerces.internal.dom.CoreDocumentImpl";

  protected static final String PSVI_DOCUMENT_CLASS_NAME =
      "com.sun.org.apache.xerces.internal.dom.PSVIDocumentImpl";

  /**
   * If the user stops the process, this exception will be thrown.
   */
  static final class Abort extends RuntimeException {

    private static final long serialVersionUID = 1687848994976808490L;
    static final Abort INSTANCE = new Abort();

    private Abort() {
    }

    public Throwable fillInStackTrace() {
      return this;
    }
  }

  // debugging

  private static final boolean DEBUG_EVENTS = false;
  private static final boolean DEBUG_BASEURI = false;

  //
  // Data
  //

  /**
   * DOM L3 error handler
   */
  protected DOMErrorHandlerWrapper fErrorHandler = null;

  /**
   * True if inside DTD.
   */
  protected boolean fInDTD;

  // features

  /**
   * Create entity reference nodes.
   */
  protected boolean fCreateEntityRefNodes;

  /**
   * Include ignorable whitespace.
   */
  protected boolean fIncludeIgnorableWhitespace;

  /**
   * Include Comments.
   */
  protected boolean fIncludeComments;

  /**
   * Create cdata nodes.
   */
  protected boolean fCreateCDATANodes;

  // dom information

  /**
   * The document.
   */
  protected Document fDocument;

  /**
   * The default Xerces document implementation, if used.
   */
  protected CoreDocumentImpl fDocumentImpl;

  /**
   * Whether to store PSVI information in DOM tree.
   */
  protected boolean fStorePSVI;

  /**
   * The document class name to use.
   */
  protected String fDocumentClassName;

  /**
   * The document type node.
   */
  protected DocumentType fDocumentType;

  /**
   * Current node.
   */
  protected Node fCurrentNode;
  protected CDATASection fCurrentCDATASection;
  protected EntityImpl fCurrentEntityDecl;
  protected int fDeferredEntityDecl;

  /**
   * Character buffer
   */
  protected final StringBuilder fStringBuilder = new StringBuilder(50);

  // internal subset

  /**
   * Internal subset buffer.
   */
  protected StringBuilder fInternalSubset;

  // deferred expansion data

  protected boolean fDeferNodeExpansion;
  protected boolean fNamespaceAware;
  protected DeferredDocumentImpl fDeferredDocumentImpl;
  protected int fDocumentIndex;
  protected int fDocumentTypeIndex;
  protected int fCurrentNodeIndex;
  protected int fCurrentCDATASectionIndex;

  // state

  /**
   * True if inside DTD external subset.
   */
  protected boolean fInDTDExternalSubset;

  /**
   * Root element node.
   */
  protected Node fRoot;

  /**
   * True if inside CDATA section.
   */
  protected boolean fInCDATASection;

  /**
   * True if saw the first chunk of characters
   */
  protected boolean fFirstChunk = false;


  /**
   * LSParserFilter: specifies that element with given QNAME and all its children
   * must be rejected
   */
  protected boolean fFilterReject = false;

  // data

  /**
   * Base uri stack
   */
  protected final Stack fBaseURIStack = new Stack();

  /**
   * LSParserFilter: tracks the element depth within a rejected subtree.
   */
  protected int fRejectedElementDepth = 0;

  /**
   * LSParserFilter: store depth of skipped elements
   */
  protected Stack fSkippedElemStack = null;

  /**
   * LSParserFilter: true if inside entity reference
   */
  protected boolean fInEntityRef = false;

  /**
   * Attribute QName.
   */
  private final QName fAttrQName = new QName();

  /**
   * Document locator.
   */
  private XMLLocator fLocator;

  // handlers

  protected LSParserFilter fDOMFilter = null;

  //
  // Constructors
  //

  /**
   * Default constructor.
   */
  protected AbstractDOMParser(XMLParserConfiguration config) {

    super(config);

    // add recognized features
    fConfiguration.addRecognizedFeatures(RECOGNIZED_FEATURES);

    // set default values
    fConfiguration.setFeature(CREATE_ENTITY_REF_NODES, true);
    fConfiguration.setFeature(INCLUDE_IGNORABLE_WHITESPACE, true);
    fConfiguration.setFeature(DEFER_NODE_EXPANSION, true);
    fConfiguration.setFeature(INCLUDE_COMMENTS_FEATURE, true);
    fConfiguration.setFeature(CREATE_CDATA_NODES_FEATURE, true);

    // add recognized properties
    fConfiguration.addRecognizedProperties(RECOGNIZED_PROPERTIES);

    // set default values
    fConfiguration.setProperty(DOCUMENT_CLASS_NAME,
        DEFAULT_DOCUMENT_CLASS_NAME);

  } // <init>(XMLParserConfiguration)

  /**
   * This method retrieves the name of current document class.
   */
  protected String getDocumentClassName() {
    return fDocumentClassName;
  }

  /**
   * This method allows the programmer to decide which document
   * factory to use when constructing the DOM tree. However, doing
   * so will lose the functionality of the default factory. Also,
   * a document class other than the default will lose the ability
   * to defer node expansion on the DOM tree produced.
   *
   * @param documentClassName The fully qualified class name of the document factory to use when
   * constructing the DOM tree.
   * @see #getDocumentClassName
   * @see #DEFAULT_DOCUMENT_CLASS_NAME
   */
  protected void setDocumentClassName(String documentClassName) {

    // normalize class name
    if (documentClassName == null) {
      documentClassName = DEFAULT_DOCUMENT_CLASS_NAME;
    }

    if (!documentClassName.equals(DEFAULT_DOCUMENT_CLASS_NAME) &&
        !documentClassName.equals(PSVI_DOCUMENT_CLASS_NAME)) {
      // verify that this class exists and is of the right type
      try {
        Class _class = ObjectFactory.findProviderClass(documentClassName, true);
        //if (!_class.isAssignableFrom(Document.class)) {
        if (!Document.class.isAssignableFrom(_class)) {
          throw new IllegalArgumentException(
              DOMMessageFormatter.formatMessage(
                  DOMMessageFormatter.DOM_DOMAIN,
                  "InvalidDocumentClassName", new Object[]{documentClassName}));
        }
      } catch (ClassNotFoundException e) {
        throw new IllegalArgumentException(
            DOMMessageFormatter.formatMessage(
                DOMMessageFormatter.DOM_DOMAIN,
                "MissingDocumentClassName", new Object[]{documentClassName}));
      }
    }

    // set document class name
    fDocumentClassName = documentClassName;
    if (!documentClassName.equals(DEFAULT_DOCUMENT_CLASS_NAME)) {
      fDeferNodeExpansion = false;
    }

  } // setDocumentClassName(String)

  //
  // Public methods
  //

  /**
   * Returns the DOM document object.
   */
  public Document getDocument() {
    return fDocument;
  } // getDocument():Document

  /**
   * Drops all references to the last DOM which was built by this parser.
   */
  public final void dropDocumentReferences() {
    fDocument = null;
    fDocumentImpl = null;
    fDeferredDocumentImpl = null;
    fDocumentType = null;
    fCurrentNode = null;
    fCurrentCDATASection = null;
    fCurrentEntityDecl = null;
    fRoot = null;
  } // dropDocumentReferences()

  //
  // XMLDocumentParser methods
  //

  /**
   * Resets the parser state.
   *
   * @throws SAXException Thrown on initialization error.
   */
  public void reset() throws XNIException {
    super.reset();

    // get feature state
    fCreateEntityRefNodes =
        fConfiguration.getFeature(CREATE_ENTITY_REF_NODES);

    fIncludeIgnorableWhitespace =
        fConfiguration.getFeature(INCLUDE_IGNORABLE_WHITESPACE);

    fDeferNodeExpansion =
        fConfiguration.getFeature(DEFER_NODE_EXPANSION);

    fNamespaceAware = fConfiguration.getFeature(NAMESPACES);

    fIncludeComments = fConfiguration.getFeature(INCLUDE_COMMENTS_FEATURE);

    fCreateCDATANodes = fConfiguration.getFeature(CREATE_CDATA_NODES_FEATURE);

    // get property
    setDocumentClassName((String)
        fConfiguration.getProperty(DOCUMENT_CLASS_NAME));

    // reset dom information
    fDocument = null;
    fDocumentImpl = null;
    fStorePSVI = false;
    fDocumentType = null;
    fDocumentTypeIndex = -1;
    fDeferredDocumentImpl = null;
    fCurrentNode = null;

    // reset string buffer
    fStringBuilder.setLength(0);

    // reset state information
    fRoot = null;
    fInDTD = false;
    fInDTDExternalSubset = false;
    fInCDATASection = false;
    fFirstChunk = false;
    fCurrentCDATASection = null;
    fCurrentCDATASectionIndex = -1;

    fBaseURIStack.removeAllElements();


  } // reset()

  /**
   * Set the locale to use for messages.
   *
   * @param locale The locale object to use for localization of messages.
   */
  public void setLocale(Locale locale) {
    fConfiguration.setLocale(locale);

  } // setLocale(Locale)

  //
  // XMLDocumentHandler methods
  //

  /**
   * This method notifies the start of a general entity.
   * <p>
   * <strong>Note:</strong> This method is not called for entity references
   * appearing as part of attribute values.
   *
   * @param name The name of the general entity.
   * @param identifier The resource identifier.
   * @param encoding The auto-detected IANA encoding name of the entity stream. This value will be
   * null in those situations where the entity encoding is not auto-detected (e.g. internal entities
   * or a document entity that is parsed from a java.io.Reader).
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void startGeneralEntity(String name,
      XMLResourceIdentifier identifier,
      String encoding, Augmentations augs)
      throws XNIException {
    if (DEBUG_EVENTS) {
      System.out.println("==>startGeneralEntity (" + name + ")");
      if (DEBUG_BASEURI) {
        System.out.println("   expandedSystemId( **baseURI): " + identifier.getExpandedSystemId());
        System.out.println("   baseURI:" + identifier.getBaseSystemId());
      }
    }

    // Always create entity reference nodes to be able to recreate
    // entity as a part of doctype
    if (!fDeferNodeExpansion) {
      if (fFilterReject) {
        return;
      }
      setCharacterData(true);
      EntityReference er = fDocument.createEntityReference(name);
      if (fDocumentImpl != null) {
        // REVISIT: baseURI/actualEncoding
        //         remove dependency on our implementation when DOM L3 is REC
        //

        EntityReferenceImpl erImpl = (EntityReferenceImpl) er;

        // set base uri
        erImpl.setBaseURI(identifier.getExpandedSystemId());
        if (fDocumentType != null) {
          // set actual encoding
          NamedNodeMap entities = fDocumentType.getEntities();
          fCurrentEntityDecl = (EntityImpl) entities.getNamedItem(name);
          if (fCurrentEntityDecl != null) {
            fCurrentEntityDecl.setInputEncoding(encoding);
          }

        }
        // we don't need synchronization now, because entity ref will be
        // expanded anyway. Synch only needed when user creates entityRef node
        erImpl.needsSyncChildren(false);
      }
      fInEntityRef = true;
      fCurrentNode.appendChild(er);
      fCurrentNode = er;
    } else {

      int er =
          fDeferredDocumentImpl
              .createDeferredEntityReference(name, identifier.getExpandedSystemId());
      if (fDocumentTypeIndex != -1) {
        // find corresponding Entity decl
        int node = fDeferredDocumentImpl.getLastChild(fDocumentTypeIndex, false);
        while (node != -1) {
          short nodeType = fDeferredDocumentImpl.getNodeType(node, false);
          if (nodeType == Node.ENTITY_NODE) {
            String nodeName =
                fDeferredDocumentImpl.getNodeName(node, false);
            if (nodeName.equals(name)) {
              fDeferredEntityDecl = node;
              fDeferredDocumentImpl.setInputEncoding(node, encoding);
              break;
            }
          }
          node = fDeferredDocumentImpl.getRealPrevSibling(node, false);
        }
      }
      fDeferredDocumentImpl.appendChild(fCurrentNodeIndex, er);
      fCurrentNodeIndex = er;
    }

  } // startGeneralEntity(String,XMLResourceIdentifier, Augmentations)

  /**
   * Notifies of the presence of a TextDecl line in an entity. If present,
   * this method will be called immediately following the startEntity call.
   * <p>
   * <strong>Note:</strong> This method will never be called for the
   * document entity; it is only called for external general entities
   * referenced in document content.
   * <p>
   * <strong>Note:</strong> This method is not called for entity references
   * appearing as part of attribute values.
   *
   * @param version The XML version, or null if not specified.
   * @param encoding The IANA encoding name of the entity.
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void textDecl(String version, String encoding, Augmentations augs) throws XNIException {
    if (fInDTD) {
      return;
    }
    if (!fDeferNodeExpansion) {
      if (fCurrentEntityDecl != null && !fFilterReject) {
        fCurrentEntityDecl.setXmlEncoding(encoding);
        if (version != null) {
          fCurrentEntityDecl.setXmlVersion(version);
        }
      }
    } else {
      if (fDeferredEntityDecl != -1) {
        fDeferredDocumentImpl.setEntityInfo(fDeferredEntityDecl, version, encoding);
      }
    }
  } // textDecl(String,String)

  /**
   * A comment.
   *
   * @param text The text in the comment.
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by application to signal an error.
   */
  public void comment(XMLString text, Augmentations augs) throws XNIException {
    if (fInDTD) {
      if (fInternalSubset != null && !fInDTDExternalSubset) {
        fInternalSubset.append("<!--");
        if (text.length > 0) {
          fInternalSubset.append(text.ch, text.offset, text.length);
        }
        fInternalSubset.append("-->");
      }
      return;
    }
    if (!fIncludeComments || fFilterReject) {
      return;
    }
    if (!fDeferNodeExpansion) {
      Comment comment = fDocument.createComment(text.toString());

      setCharacterData(false);
      fCurrentNode.appendChild(comment);
      if (fDOMFilter != null && !fInEntityRef &&
          (fDOMFilter.getWhatToShow() & NodeFilter.SHOW_COMMENT) != 0) {
        short code = fDOMFilter.acceptNode(comment);
        switch (code) {
          case LSParserFilter.FILTER_INTERRUPT: {
            throw Abort.INSTANCE;
          }
          case LSParserFilter.FILTER_REJECT: {
            // REVISIT: the constant FILTER_REJECT should be changed when new
            // DOM LS specs gets published

            // fall through to SKIP since comment has no children.
          }
          case LSParserFilter.FILTER_SKIP: {
            // REVISIT: the constant FILTER_SKIP should be changed when new
            // DOM LS specs gets published
            fCurrentNode.removeChild(comment);
            // make sure we don't loose chars if next event is characters()
            fFirstChunk = true;
            return;
          }

          default: {
            // accept node
          }
        }
      }

    } else {
      int comment =
          fDeferredDocumentImpl.createDeferredComment(text.toString());
      fDeferredDocumentImpl.appendChild(fCurrentNodeIndex, comment);
    }

  } // comment(XMLString)

  /**
   * A processing instruction. Processing instructions consist of a
   * target name and, optionally, text data. The data is only meaningful
   * to the application.
   * <p>
   * Typically, a processing instruction's data will contain a series
   * of pseudo-attributes. These pseudo-attributes follow the form of
   * element attributes but are <strong>not</strong> parsed or presented
   * to the application as anything other than text. The application is
   * responsible for parsing the data.
   *
   * @param target The target.
   * @param data The data or null if none specified.
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void processingInstruction(String target, XMLString data, Augmentations augs)
      throws XNIException {

    if (fInDTD) {
      if (fInternalSubset != null && !fInDTDExternalSubset) {
        fInternalSubset.append("<?");
        fInternalSubset.append(target);
        if (data.length > 0) {
          fInternalSubset.append(' ').append(data.ch, data.offset, data.length);
        }
        fInternalSubset.append("?>");
      }
      return;
    }

    if (DEBUG_EVENTS) {
      System.out.println("==>processingInstruction (" + target + ")");
    }
    if (!fDeferNodeExpansion) {
      if (fFilterReject) {
        return;
      }
      ProcessingInstruction pi =
          fDocument.createProcessingInstruction(target, data.toString());

      setCharacterData(false);
      fCurrentNode.appendChild(pi);
      if (fDOMFilter != null && !fInEntityRef &&
          (fDOMFilter.getWhatToShow() & NodeFilter.SHOW_PROCESSING_INSTRUCTION) != 0) {
        short code = fDOMFilter.acceptNode(pi);
        switch (code) {
          case LSParserFilter.FILTER_INTERRUPT: {
            throw Abort.INSTANCE;
          }
          case LSParserFilter.FILTER_REJECT: {
            // fall through to SKIP since PI has no children.
          }
          case LSParserFilter.FILTER_SKIP: {
            fCurrentNode.removeChild(pi);
            // fFirstChunk must be set to true so that data
            // won't be lost in the case where the child before PI is
            // a text node and the next event is characters.
            fFirstChunk = true;
            return;
          }
          default: {
          }
        }
      }
    } else {
      int pi = fDeferredDocumentImpl.
          createDeferredProcessingInstruction(target, data.toString());
      fDeferredDocumentImpl.appendChild(fCurrentNodeIndex, pi);
    }

  } // processingInstruction(String,XMLString)

  /**
   * The start of the document.
   *
   * @param locator The system identifier of the entity if the entity is external, null otherwise.
   * @param encoding The auto-detected IANA encoding name of the entity stream. This value will be
   * null in those situations where the entity encoding is not auto-detected (e.g. internal entities
   * or a document entity that is parsed from a java.io.Reader).
   * @param namespaceContext The namespace context in effect at the start of this document. This
   * object represents the current context. Implementors of this class are responsible for copying
   * the namespace bindings from the the current context (and its parent contexts) if that
   * information is important.
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void startDocument(XMLLocator locator, String encoding,
      NamespaceContext namespaceContext, Augmentations augs)
      throws XNIException {

    fLocator = locator;
    if (!fDeferNodeExpansion) {
      if (fDocumentClassName.equals(DEFAULT_DOCUMENT_CLASS_NAME)) {
        fDocument = new DocumentImpl();
        fDocumentImpl = (CoreDocumentImpl) fDocument;
        // REVISIT: when DOM Level 3 is REC rely on Document.support
        //          instead of specific class
        // set DOM error checking off
        fDocumentImpl.setStrictErrorChecking(false);
        // set actual encoding
        fDocumentImpl.setInputEncoding(encoding);
        // set documentURI
        fDocumentImpl.setDocumentURI(locator.getExpandedSystemId());
      } else if (fDocumentClassName.equals(PSVI_DOCUMENT_CLASS_NAME)) {
        fDocument = new PSVIDocumentImpl();
        fDocumentImpl = (CoreDocumentImpl) fDocument;
        fStorePSVI = true;
        // REVISIT: when DOM Level 3 is REC rely on Document.support
        //          instead of specific class
        // set DOM error checking off
        fDocumentImpl.setStrictErrorChecking(false);
        // set actual encoding
        fDocumentImpl.setInputEncoding(encoding);
        // set documentURI
        fDocumentImpl.setDocumentURI(locator.getExpandedSystemId());
      } else {
        // use specified document class
        try {
          Class documentClass = ObjectFactory.findProviderClass(fDocumentClassName, true);
          fDocument = (Document) documentClass.newInstance();

          // if subclass of our own class that's cool too
          Class defaultDocClass =
              ObjectFactory.findProviderClass(CORE_DOCUMENT_CLASS_NAME, true);
          if (defaultDocClass.isAssignableFrom(documentClass)) {
            fDocumentImpl = (CoreDocumentImpl) fDocument;

            Class psviDocClass = ObjectFactory.findProviderClass(PSVI_DOCUMENT_CLASS_NAME, true);
            if (psviDocClass.isAssignableFrom(documentClass)) {
              fStorePSVI = true;
            }

            // REVISIT: when DOM Level 3 is REC rely on
            //          Document.support instead of specific class
            // set DOM error checking off
            fDocumentImpl.setStrictErrorChecking(false);
            // set actual encoding
            fDocumentImpl.setInputEncoding(encoding);
            // set documentURI
            if (locator != null) {
              fDocumentImpl.setDocumentURI(locator.getExpandedSystemId());
            }
          }
        } catch (ClassNotFoundException e) {
          // won't happen we already checked that earlier
        } catch (Exception e) {
          throw new RuntimeException(
              DOMMessageFormatter.formatMessage(
                  DOMMessageFormatter.DOM_DOMAIN,
                  "CannotCreateDocumentClass",
                  new Object[]{fDocumentClassName}));
        }
      }
      fCurrentNode = fDocument;
    } else {
      fDeferredDocumentImpl = new DeferredDocumentImpl(fNamespaceAware);
      fDocument = fDeferredDocumentImpl;
      fDocumentIndex = fDeferredDocumentImpl.createDeferredDocument();
      // REVISIT: strict error checking is not implemented in deferred dom.
      //          Document.support instead of specific class

      // set actual encoding
      fDeferredDocumentImpl.setInputEncoding(encoding);
      // set documentURI
      fDeferredDocumentImpl.setDocumentURI(locator.getExpandedSystemId());
      fCurrentNodeIndex = fDocumentIndex;

    }

  } // startDocument(String,String)

  /**
   * Notifies of the presence of an XMLDecl line in the document. If
   * present, this method will be called immediately following the
   * startDocument call.
   *
   * @param version The XML version.
   * @param encoding The IANA encoding name of the document, or null if not specified.
   * @param standalone The standalone value, or null if not specified.
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void xmlDecl(String version, String encoding, String standalone,
      Augmentations augs)
      throws XNIException {
    if (!fDeferNodeExpansion) {
      // REVISIT: when DOM Level 3 is REC rely on Document.support
      //          instead of specific class
      if (fDocumentImpl != null) {
        if (version != null) {
          fDocumentImpl.setXmlVersion(version);
        }
        fDocumentImpl.setXmlEncoding(encoding);
        fDocumentImpl.setXmlStandalone("yes".equals(standalone));
      }
    } else {
      if (version != null) {
        fDeferredDocumentImpl.setXmlVersion(version);
      }
      fDeferredDocumentImpl.setXmlEncoding(encoding);
      fDeferredDocumentImpl.setXmlStandalone("yes".equals(standalone));
    }
  } // xmlDecl(String,String,String)

  /**
   * Notifies of the presence of the DOCTYPE line in the document.
   *
   * @param rootElement The name of the root element.
   * @param publicId The public identifier if an external DTD or null if the external DTD is
   * specified using SYSTEM.
   * @param systemId The system identifier if an external DTD, null otherwise.
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void doctypeDecl(String rootElement,
      String publicId, String systemId, Augmentations augs)
      throws XNIException {

    if (!fDeferNodeExpansion) {
      if (fDocumentImpl != null) {
        fDocumentType = fDocumentImpl.createDocumentType(
            rootElement, publicId, systemId);
        fCurrentNode.appendChild(fDocumentType);
      }
    } else {
      fDocumentTypeIndex = fDeferredDocumentImpl.
          createDeferredDocumentType(rootElement, publicId, systemId);
      fDeferredDocumentImpl.appendChild(fCurrentNodeIndex, fDocumentTypeIndex);
    }

  } // doctypeDecl(String,String,String)

  /**
   * The start of an element. If the document specifies the start element
   * by using an empty tag, then the startElement method will immediately
   * be followed by the endElement method, with no intervening methods.
   *
   * @param element The name of the element.
   * @param attributes The element attributes.
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void startElement(QName element, XMLAttributes attributes, Augmentations augs)
      throws XNIException {
    if (DEBUG_EVENTS) {
      System.out.println("==>startElement (" + element.rawname + ")");
    }
    if (!fDeferNodeExpansion) {
      if (fFilterReject) {
        ++fRejectedElementDepth;
        return;
      }
      Element el = createElementNode(element);
      int attrCount = attributes.getLength();
      boolean seenSchemaDefault = false;
      for (int i = 0; i < attrCount; i++) {
        attributes.getName(i, fAttrQName);
        Attr attr = createAttrNode(fAttrQName);

        String attrValue = attributes.getValue(i);

        AttributePSVI attrPSVI = (AttributePSVI) attributes.getAugmentations(i)
            .getItem(Constants.ATTRIBUTE_PSVI);
        if (fStorePSVI && attrPSVI != null) {
          ((PSVIAttrNSImpl) attr).setPSVI(attrPSVI);
        }

        attr.setValue(attrValue);
        boolean specified = attributes.isSpecified(i);
        // Take special care of schema defaulted attributes. Calling the
        // non-namespace aware setAttributeNode() method could overwrite
        // another attribute with the same local name.
        if (!specified && (seenSchemaDefault || (fAttrQName.uri != null &&
            fAttrQName.uri != NamespaceContext.XMLNS_URI && fAttrQName.prefix == null))) {
          el.setAttributeNodeNS(attr);
          seenSchemaDefault = true;
        } else {
          el.setAttributeNode(attr);
        }
        // NOTE: The specified value MUST be set after you set
        //       the node value because that turns the "specified"
        //       flag to "true" which may overwrite a "false"
        //       value from the attribute list. -Ac
        if (fDocumentImpl != null) {
          AttrImpl attrImpl = (AttrImpl) attr;
          Object type = null;
          boolean id = false;

          // REVISIT: currently it is possible that someone turns off
          // namespaces and turns on xml schema validation
          // To avoid classcast exception in AttrImpl check for namespaces
          // however the correct solution should probably disallow setting
          // namespaces to false when schema processing is turned on.
          if (attrPSVI != null && fNamespaceAware) {
            // XML Schema
            type = attrPSVI.getMemberTypeDefinition();
            if (type == null) {
              type = attrPSVI.getTypeDefinition();
              if (type != null) {
                id = ((XSSimpleType) type).isIDType();
                attrImpl.setType(type);
              }
            } else {
              id = ((XSSimpleType) type).isIDType();
              attrImpl.setType(type);
            }
          } else {
            // DTD
            boolean isDeclared = Boolean.TRUE
                .equals(attributes.getAugmentations(i).getItem(Constants.ATTRIBUTE_DECLARED));
            // For DOM Level 3 TypeInfo, the type name must
            // be null if this attribute has not been declared
            // in the DTD.
            if (isDeclared) {
              type = attributes.getType(i);
              id = "ID".equals(type);
            }
            attrImpl.setType(type);
          }

          if (id) {
            ((ElementImpl) el).setIdAttributeNode(attr, true);
          }

          attrImpl.setSpecified(specified);
          // REVISIT: Handle entities in attribute value.
        }
      }
      setCharacterData(false);

      if (augs != null) {
        ElementPSVI elementPSVI = (ElementPSVI) augs.getItem(Constants.ELEMENT_PSVI);
        if (elementPSVI != null && fNamespaceAware) {
          XSTypeDefinition type = elementPSVI.getMemberTypeDefinition();
          if (type == null) {
            type = elementPSVI.getTypeDefinition();
          }
          ((ElementNSImpl) el).setType(type);
        }
      }

      // filter nodes
      if (fDOMFilter != null && !fInEntityRef) {
        if (fRoot == null) {
          // fill value of the root element
          fRoot = el;
        } else {
          short code = fDOMFilter.startElement(el);
          switch (code) {
            case LSParserFilter.FILTER_INTERRUPT: {
              throw Abort.INSTANCE;
            }
            case LSParserFilter.FILTER_REJECT: {
              fFilterReject = true;
              fRejectedElementDepth = 0;
              return;
            }
            case LSParserFilter.FILTER_SKIP: {
              // make sure that if any char data is available
              // the fFirstChunk is true, so that if the next event
              // is characters(), and the last node is text, we will copy
              // the value already in the text node to fStringBuffer
              // (not to lose it).
              fFirstChunk = true;
              fSkippedElemStack.push(Boolean.TRUE);
              return;
            }
            default: {
              if (!fSkippedElemStack.isEmpty()) {
                fSkippedElemStack.push(Boolean.FALSE);
              }
            }
          }
        }
      }
      fCurrentNode.appendChild(el);
      fCurrentNode = el;
    } else {
      int el = fDeferredDocumentImpl.createDeferredElement(fNamespaceAware ?
          element.uri : null, element.rawname);
      Object type = null;
      int attrCount = attributes.getLength();
      // Need to loop in reverse order so that the attributes
      // are processed in document order when the DOM is expanded.
      for (int i = attrCount - 1; i >= 0; --i) {

        // set type information
        AttributePSVI attrPSVI = (AttributePSVI) attributes.getAugmentations(i)
            .getItem(Constants.ATTRIBUTE_PSVI);
        boolean id = false;

        // REVISIT: currently it is possible that someone turns off
        // namespaces and turns on xml schema validation
        // To avoid classcast exception in AttrImpl check for namespaces
        // however the correct solution should probably disallow setting
        // namespaces to false when schema processing is turned on.
        if (attrPSVI != null && fNamespaceAware) {
          // XML Schema
          type = attrPSVI.getMemberTypeDefinition();
          if (type == null) {
            type = attrPSVI.getTypeDefinition();
            if (type != null) {
              id = ((XSSimpleType) type).isIDType();
            }
          } else {
            id = ((XSSimpleType) type).isIDType();
          }
        } else {
          // DTD
          boolean isDeclared = Boolean.TRUE
              .equals(attributes.getAugmentations(i).getItem(Constants.ATTRIBUTE_DECLARED));
          // For DOM Level 3 TypeInfo, the type name must
          // be null if this attribute has not been declared
          // in the DTD.
          if (isDeclared) {
            type = attributes.getType(i);
            id = "ID".equals(type);
          }
        }

        // create attribute
        fDeferredDocumentImpl.setDeferredAttribute(
            el,
            attributes.getQName(i),
            attributes.getURI(i),
            attributes.getValue(i),
            attributes.isSpecified(i),
            id,
            type);
      }

      fDeferredDocumentImpl.appendChild(fCurrentNodeIndex, el);
      fCurrentNodeIndex = el;
    }
  } // startElement(QName,XMLAttributes)


  /**
   * An empty element.
   *
   * @param element The name of the element.
   * @param attributes The element attributes.
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void emptyElement(QName element, XMLAttributes attributes, Augmentations augs)
      throws XNIException {

    startElement(element, attributes, augs);
    endElement(element, augs);

  } // emptyElement(QName,XMLAttributes)

  /**
   * Character content.
   *
   * @param text The content.
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void characters(XMLString text, Augmentations augs) throws XNIException {

    if (DEBUG_EVENTS) {
      System.out.println("==>characters(): " + text.toString());
    }

    if (!fDeferNodeExpansion) {

      if (fFilterReject) {
        return;
      }
      if (fInCDATASection && fCreateCDATANodes) {
        if (fCurrentCDATASection == null) {
          fCurrentCDATASection =
              fDocument.createCDATASection(text.toString());
          fCurrentNode.appendChild(fCurrentCDATASection);
          fCurrentNode = fCurrentCDATASection;
        } else {
          fCurrentCDATASection.appendData(text.toString());
        }
      } else if (!fInDTD) {
        // if type is union (XML Schema) it is possible that we receive
        // character call with empty data
        if (text.length == 0) {
          return;
        }

        Node child = fCurrentNode.getLastChild();
        if (child != null && child.getNodeType() == Node.TEXT_NODE) {
          // collect all the data into the string buffer.
          if (fFirstChunk) {
            if (fDocumentImpl != null) {
              fStringBuilder.append(((TextImpl) child).removeData());
            } else {
              fStringBuilder.append(((Text) child).getData());
              ((Text) child).setNodeValue(null);
            }
            fFirstChunk = false;
          }
          if (text.length > 0) {
            fStringBuilder.append(text.ch, text.offset, text.length);
          }
        } else {
          fFirstChunk = true;
          Text textNode = fDocument.createTextNode(text.toString());
          fCurrentNode.appendChild(textNode);
        }

      }
    } else {
      // The Text and CDATASection normalization is taken care of within
      // the DOM in the deferred case.
      if (fInCDATASection && fCreateCDATANodes) {
        if (fCurrentCDATASectionIndex == -1) {
          int cs = fDeferredDocumentImpl.
              createDeferredCDATASection(text.toString());

          fDeferredDocumentImpl.appendChild(fCurrentNodeIndex, cs);
          fCurrentCDATASectionIndex = cs;
          fCurrentNodeIndex = cs;
        } else {
          int txt = fDeferredDocumentImpl.
              createDeferredTextNode(text.toString(), false);
          fDeferredDocumentImpl.appendChild(fCurrentNodeIndex, txt);
        }
      } else if (!fInDTD) {
        // if type is union (XML Schema) it is possible that we receive
        // character call with empty data
        if (text.length == 0) {
          return;
        }

        String value = text.toString();
        int txt = fDeferredDocumentImpl.
            createDeferredTextNode(value, false);
        fDeferredDocumentImpl.appendChild(fCurrentNodeIndex, txt);

      }
    }
  } // characters(XMLString)

  /**
   * Ignorable whitespace. For this method to be called, the document
   * source must have some way of determining that the text containing
   * only whitespace characters should be considered ignorable. For
   * example, the validator can determine if a length of whitespace
   * characters in the document are ignorable based on the element
   * content model.
   *
   * @param text The ignorable whitespace.
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void ignorableWhitespace(XMLString text, Augmentations augs) throws XNIException {

    if (!fIncludeIgnorableWhitespace || fFilterReject) {
      return;
    }
    if (!fDeferNodeExpansion) {
      Node child = fCurrentNode.getLastChild();
      if (child != null && child.getNodeType() == Node.TEXT_NODE) {
        Text textNode = (Text) child;
        textNode.appendData(text.toString());
      } else {
        Text textNode = fDocument.createTextNode(text.toString());
        if (fDocumentImpl != null) {
          TextImpl textNodeImpl = (TextImpl) textNode;
          textNodeImpl.setIgnorableWhitespace(true);
        }
        fCurrentNode.appendChild(textNode);
      }
    } else {
      // The Text normalization is taken care of within the DOM in the
      // deferred case.
      int txt = fDeferredDocumentImpl.
          createDeferredTextNode(text.toString(), true);
      fDeferredDocumentImpl.appendChild(fCurrentNodeIndex, txt);
    }

  } // ignorableWhitespace(XMLString)

  /**
   * The end of an element.
   *
   * @param element The name of the element.
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void endElement(QName element, Augmentations augs) throws XNIException {
    if (DEBUG_EVENTS) {
      System.out.println("==>endElement (" + element.rawname + ")");
    }
    if (!fDeferNodeExpansion) {

      // REVISIT: Should this happen after we call the filter?
      if (augs != null && fDocumentImpl != null && (fNamespaceAware || fStorePSVI)) {
        ElementPSVI elementPSVI = (ElementPSVI) augs.getItem(Constants.ELEMENT_PSVI);
        if (elementPSVI != null) {
          // Updating TypeInfo. If the declared type is a union the
          // [member type definition] will only be available at the
          // end of an element.
          if (fNamespaceAware) {
            XSTypeDefinition type = elementPSVI.getMemberTypeDefinition();
            if (type == null) {
              type = elementPSVI.getTypeDefinition();
            }
            ((ElementNSImpl) fCurrentNode).setType(type);
          }
          if (fStorePSVI) {
            ((PSVIElementNSImpl) fCurrentNode).setPSVI(elementPSVI);
          }
        }
      }

      if (fDOMFilter != null) {
        if (fFilterReject) {
          if (fRejectedElementDepth-- == 0) {
            fFilterReject = false;
          }
          return;
        }
        if (!fSkippedElemStack.isEmpty()) {
          if (fSkippedElemStack.pop() == Boolean.TRUE) {
            return;
          }
        }
        setCharacterData(false);
        if ((fCurrentNode != fRoot) && !fInEntityRef
            && (fDOMFilter.getWhatToShow() & NodeFilter.SHOW_ELEMENT) != 0) {
          short code = fDOMFilter.acceptNode(fCurrentNode);
          switch (code) {
            case LSParserFilter.FILTER_INTERRUPT: {
              throw Abort.INSTANCE;
            }
            case LSParserFilter.FILTER_REJECT: {
              Node parent = fCurrentNode.getParentNode();
              parent.removeChild(fCurrentNode);
              fCurrentNode = parent;
              return;
            }
            case LSParserFilter.FILTER_SKIP: {
              // make sure that if any char data is available
              // the fFirstChunk is true, so that if the next event
              // is characters(), and the last node is text, we will copy
              // the value already in the text node to fStringBuffer
              // (not to lose it).
              fFirstChunk = true;

              // replace children
              Node parent = fCurrentNode.getParentNode();
              NodeList ls = fCurrentNode.getChildNodes();
              int length = ls.getLength();

              for (int i = 0; i < length; i++) {
                parent.appendChild(ls.item(0));
              }
              parent.removeChild(fCurrentNode);
              fCurrentNode = parent;

              return;
            }

            default: {
            }
          }
        }
        fCurrentNode = fCurrentNode.getParentNode();

      } // end-if DOMFilter
      else {
        setCharacterData(false);
        fCurrentNode = fCurrentNode.getParentNode();
      }

    } else {
      if (augs != null) {
        ElementPSVI elementPSVI = (ElementPSVI) augs.getItem(Constants.ELEMENT_PSVI);
        if (elementPSVI != null) {
          // Setting TypeInfo. If the declared type is a union the
          // [member type definition] will only be available at the
          // end of an element.
          XSTypeDefinition type = elementPSVI.getMemberTypeDefinition();
          if (type == null) {
            type = elementPSVI.getTypeDefinition();
          }
          fDeferredDocumentImpl.setTypeInfo(fCurrentNodeIndex, type);
        }
      }
      fCurrentNodeIndex =
          fDeferredDocumentImpl.getParentNode(fCurrentNodeIndex, false);
    }


  } // endElement(QName)


  /**
   * The start of a CDATA section.
   *
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void startCDATA(Augmentations augs) throws XNIException {

    fInCDATASection = true;
    if (!fDeferNodeExpansion) {
      if (fFilterReject) {
        return;
      }
      if (fCreateCDATANodes) {
        setCharacterData(false);
      }
    }
  } // startCDATA()

  /**
   * The end of a CDATA section.
   *
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void endCDATA(Augmentations augs) throws XNIException {

    fInCDATASection = false;
    if (!fDeferNodeExpansion) {

      if (fFilterReject) {
        return;
      }

      if (fCurrentCDATASection != null) {

        if (fDOMFilter != null && !fInEntityRef &&
            (fDOMFilter.getWhatToShow() & NodeFilter.SHOW_CDATA_SECTION) != 0) {
          short code = fDOMFilter.acceptNode(fCurrentCDATASection);
          switch (code) {
            case LSParserFilter.FILTER_INTERRUPT: {
              throw Abort.INSTANCE;
            }
            case LSParserFilter.FILTER_REJECT: {
              // fall through to SKIP since CDATA section has no children.
            }
            case LSParserFilter.FILTER_SKIP: {
              Node parent = fCurrentNode.getParentNode();
              parent.removeChild(fCurrentCDATASection);
              fCurrentNode = parent;
              return;
            }

            default: {
              // accept node
            }
          }
        }

        fCurrentNode = fCurrentNode.getParentNode();
        fCurrentCDATASection = null;
      }
    } else {
      if (fCurrentCDATASectionIndex != -1) {
        fCurrentNodeIndex =
            fDeferredDocumentImpl.getParentNode(fCurrentNodeIndex, false);
        fCurrentCDATASectionIndex = -1;
      }
    }

  } // endCDATA()

  /**
   * The end of the document.
   *
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void endDocument(Augmentations augs) throws XNIException {

    if (!fDeferNodeExpansion) {
      // REVISIT: when DOM Level 3 is REC rely on Document.support
      //          instead of specific class
      // set the actual encoding and set DOM error checking back on
      if (fDocumentImpl != null) {
        if (fLocator != null) {
          if (fLocator.getEncoding() != null) {
            fDocumentImpl.setInputEncoding(fLocator.getEncoding());
          }
        }
        fDocumentImpl.setStrictErrorChecking(true);
      }
      fCurrentNode = null;
    } else {
      // set the actual encoding
      if (fLocator != null) {
        if (fLocator.getEncoding() != null) {
          fDeferredDocumentImpl.setInputEncoding(fLocator.getEncoding());
        }
      }
      fCurrentNodeIndex = -1;
    }

  } // endDocument()

  /**
   * This method notifies the end of a general entity.
   * <p>
   * <strong>Note:</strong> This method is not called for entity references
   * appearing as part of attribute values.
   *
   * @param name The name of the entity.
   * @param augs Additional information that may include infoset augmentations
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void endGeneralEntity(String name, Augmentations augs) throws XNIException {
    if (DEBUG_EVENTS) {
      System.out.println("==>endGeneralEntity: (" + name + ")");
    }
    if (!fDeferNodeExpansion) {

      if (fFilterReject) {
        return;
      }
      setCharacterData(true);

      if (fDocumentType != null) {
        // get current entity declaration
        NamedNodeMap entities = fDocumentType.getEntities();
        fCurrentEntityDecl = (EntityImpl) entities.getNamedItem(name);
        if (fCurrentEntityDecl != null) {
          if (fCurrentEntityDecl != null && fCurrentEntityDecl.getFirstChild() == null) {
            fCurrentEntityDecl.setReadOnly(false, true);
            Node child = fCurrentNode.getFirstChild();
            while (child != null) {
              Node copy = child.cloneNode(true);
              fCurrentEntityDecl.appendChild(copy);
              child = child.getNextSibling();
            }
            fCurrentEntityDecl.setReadOnly(true, true);

            //entities.setNamedItem(fCurrentEntityDecl);
          }
          fCurrentEntityDecl = null;
        }

      }
      fInEntityRef = false;
      boolean removeEntityRef = false;
      if (fCreateEntityRefNodes) {
        if (fDocumentImpl != null) {
          // Make entity ref node read only
          ((NodeImpl) fCurrentNode).setReadOnly(true, true);
        }

        if (fDOMFilter != null &&
            (fDOMFilter.getWhatToShow() & NodeFilter.SHOW_ENTITY_REFERENCE) != 0) {
          short code = fDOMFilter.acceptNode(fCurrentNode);
          switch (code) {
            case LSParserFilter.FILTER_INTERRUPT: {
              throw Abort.INSTANCE;
            }
            case LSParserFilter.FILTER_REJECT: {
              Node parent = fCurrentNode.getParentNode();
              parent.removeChild(fCurrentNode);
              fCurrentNode = parent;
              return;

            }
            case LSParserFilter.FILTER_SKIP: {
              // make sure we don't loose chars if next event is characters()
              fFirstChunk = true;
              removeEntityRef = true;
              break;
            }

            default: {
              fCurrentNode = fCurrentNode.getParentNode();
            }
          }
        } else {
          fCurrentNode = fCurrentNode.getParentNode();
        }
      }

      if (!fCreateEntityRefNodes || removeEntityRef) {
        // move entity reference children to the list of
        // siblings of its parent and remove entity reference
        NodeList children = fCurrentNode.getChildNodes();
        Node parent = fCurrentNode.getParentNode();
        int length = children.getLength();
        if (length > 0) {

          // get previous sibling of the entity reference
          Node node = fCurrentNode.getPreviousSibling();
          // normalize text nodes
          Node child = children.item(0);
          if (node != null && node.getNodeType() == Node.TEXT_NODE &&
              child.getNodeType() == Node.TEXT_NODE) {
            ((Text) node).appendData(child.getNodeValue());
            fCurrentNode.removeChild(child);

          } else {
            node = parent.insertBefore(child, fCurrentNode);
            handleBaseURI(node);
          }

          for (int i = 1; i < length; i++) {
            node = parent.insertBefore(children.item(0), fCurrentNode);
            handleBaseURI(node);
          }
        } // length > 0
        parent.removeChild(fCurrentNode);
        fCurrentNode = parent;
      }
    } else {

      if (fDocumentTypeIndex != -1) {
        // find corresponding Entity decl
        int node = fDeferredDocumentImpl.getLastChild(fDocumentTypeIndex, false);
        while (node != -1) {
          short nodeType = fDeferredDocumentImpl.getNodeType(node, false);
          if (nodeType == Node.ENTITY_NODE) {
            String nodeName =
                fDeferredDocumentImpl.getNodeName(node, false);
            if (nodeName.equals(name)) {
              fDeferredEntityDecl = node;
              break;
            }
          }
          node = fDeferredDocumentImpl.getRealPrevSibling(node, false);
        }
      }

      if (fDeferredEntityDecl != -1 &&
          fDeferredDocumentImpl.getLastChild(fDeferredEntityDecl, false) == -1) {
        // entity definition exists and it does not have any children
        int prevIndex = -1;
        int childIndex = fDeferredDocumentImpl.getLastChild(fCurrentNodeIndex, false);
        while (childIndex != -1) {
          int cloneIndex = fDeferredDocumentImpl.cloneNode(childIndex, true);
          fDeferredDocumentImpl.insertBefore(fDeferredEntityDecl, cloneIndex, prevIndex);
          prevIndex = cloneIndex;
          childIndex = fDeferredDocumentImpl.getRealPrevSibling(childIndex, false);
        }
      }
      if (fCreateEntityRefNodes) {
        fCurrentNodeIndex =
            fDeferredDocumentImpl.getParentNode(fCurrentNodeIndex,
                false);
      } else { //!fCreateEntityRefNodes
        // move children of entity ref before the entity ref.
        // remove entity ref.

        // holds a child of entity ref
        int childIndex = fDeferredDocumentImpl.getLastChild(fCurrentNodeIndex, false);
        int parentIndex =
            fDeferredDocumentImpl.getParentNode(fCurrentNodeIndex,
                false);

        int prevIndex = fCurrentNodeIndex;
        int lastChild = childIndex;
        int sibling = -1;
        while (childIndex != -1) {
          handleBaseURI(childIndex);
          sibling = fDeferredDocumentImpl.getRealPrevSibling(childIndex, false);
          fDeferredDocumentImpl.insertBefore(parentIndex, childIndex, prevIndex);
          prevIndex = childIndex;
          childIndex = sibling;
        }
        if (lastChild != -1) {
          fDeferredDocumentImpl.setAsLastChild(parentIndex, lastChild);
        } else {
          sibling = fDeferredDocumentImpl.getRealPrevSibling(prevIndex, false);
          fDeferredDocumentImpl.setAsLastChild(parentIndex, sibling);
        }
        fCurrentNodeIndex = parentIndex;
      }
      fDeferredEntityDecl = -1;
    }


  } // endGeneralEntity(String, Augmentations)


  /**
   * Record baseURI information for the Element (by adding xml:base attribute)
   * or for the ProcessingInstruction (by setting a baseURI field)
   * Non deferred DOM.
   */
  protected final void handleBaseURI(Node node) {
    if (fDocumentImpl != null) {
      // REVISIT: remove dependency on our implementation when
      //          DOM L3 becomes REC

      String baseURI = null;
      short nodeType = node.getNodeType();

      if (nodeType == Node.ELEMENT_NODE) {
        // if an element already has xml:base attribute
        // do nothing
        if (fNamespaceAware) {
          if (((Element) node).getAttributeNodeNS("http://www.w3.org/XML/1998/namespace", "base")
              != null) {
            return;
          }
        } else if (((Element) node).getAttributeNode("xml:base") != null) {
          return;
        }
        // retrive the baseURI from the entity reference
        baseURI = ((EntityReferenceImpl) fCurrentNode).getBaseURI();
        if (baseURI != null && !baseURI.equals(fDocumentImpl.getDocumentURI())) {
          if (fNamespaceAware) {
            ((Element) node)
                .setAttributeNS("http://www.w3.org/XML/1998/namespace", "xml:base", baseURI);
          } else {
            ((Element) node).setAttribute("xml:base", baseURI);
          }
        }
      } else if (nodeType == Node.PROCESSING_INSTRUCTION_NODE) {

        baseURI = ((EntityReferenceImpl) fCurrentNode).getBaseURI();
        if (baseURI != null && fErrorHandler != null) {
          DOMErrorImpl error = new DOMErrorImpl();
          error.fType = "pi-base-uri-not-preserved";
          error.fRelatedData = baseURI;
          error.fSeverity = DOMError.SEVERITY_WARNING;
          fErrorHandler.getErrorHandler().handleError(error);
        }
      }
    }
  }

  /**
   * Record baseURI information for the Element (by adding xml:base attribute)
   * or for the ProcessingInstruction (by setting a baseURI field)
   * Deferred DOM.
   */
  protected final void handleBaseURI(int node) {
    short nodeType = fDeferredDocumentImpl.getNodeType(node, false);

    if (nodeType == Node.ELEMENT_NODE) {
      String baseURI = fDeferredDocumentImpl.getNodeValueString(fCurrentNodeIndex, false);
      if (baseURI == null) {
        baseURI = fDeferredDocumentImpl.getDeferredEntityBaseURI(fDeferredEntityDecl);
      }
      if (baseURI != null && !baseURI.equals(fDeferredDocumentImpl.getDocumentURI())) {
        fDeferredDocumentImpl.setDeferredAttribute(node,
            "xml:base",
            "http://www.w3.org/XML/1998/namespace",
            baseURI,
            true);
      }
    } else if (nodeType == Node.PROCESSING_INSTRUCTION_NODE) {

      // retrieve baseURI from the entity reference
      String baseURI = fDeferredDocumentImpl.getNodeValueString(fCurrentNodeIndex, false);

      if (baseURI == null) {
        // try baseURI of the entity declaration
        baseURI = fDeferredDocumentImpl.getDeferredEntityBaseURI(fDeferredEntityDecl);
      }

      if (baseURI != null && fErrorHandler != null) {
        DOMErrorImpl error = new DOMErrorImpl();
        error.fType = "pi-base-uri-not-preserved";
        error.fRelatedData = baseURI;
        error.fSeverity = DOMError.SEVERITY_WARNING;
        fErrorHandler.getErrorHandler().handleError(error);
      }
    }
  }

  //
  // XMLDTDHandler methods
  //

  /**
   * The start of the DTD.
   *
   * @param locator The document locator, or null if the document location cannot be reported during
   * the parsing of the document DTD. However, it is <em>strongly</em> recommended that a locator be
   * supplied that can at least report the base system identifier of the DTD.
   * @param augs Additional information that may include infoset augmentations.
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void startDTD(XMLLocator locator, Augmentations augs) throws XNIException {
    if (DEBUG_EVENTS) {
      System.out.println("==>startDTD");
      if (DEBUG_BASEURI) {
        System.out.println("   expandedSystemId: " + locator.getExpandedSystemId());
        System.out.println("   baseURI:" + locator.getBaseSystemId());
      }
    }

    fInDTD = true;
    if (locator != null) {
      fBaseURIStack.push(locator.getBaseSystemId());
    }
    if (fDeferNodeExpansion || fDocumentImpl != null) {
      fInternalSubset = new StringBuilder(1024);
    }
  } // startDTD(XMLLocator)


  /**
   * The end of the DTD.
   *
   * @param augs Additional information that may include infoset augmentations.
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void endDTD(Augmentations augs) throws XNIException {
    if (DEBUG_EVENTS) {
      System.out.println("==>endDTD()");
    }
    fInDTD = false;
    if (!fBaseURIStack.isEmpty()) {
      fBaseURIStack.pop();
    }
    String internalSubset = fInternalSubset != null && fInternalSubset.length() > 0
        ? fInternalSubset.toString() : null;
    if (fDeferNodeExpansion) {
      if (internalSubset != null) {
        fDeferredDocumentImpl.setInternalSubset(fDocumentTypeIndex, internalSubset);
      }
    } else if (fDocumentImpl != null) {
      if (internalSubset != null) {
        ((DocumentTypeImpl) fDocumentType).setInternalSubset(internalSubset);
      }
    }
  } // endDTD()

  /**
   * The start of a conditional section.
   *
   * @param type The type of the conditional section. This value will either be CONDITIONAL_INCLUDE
   * or CONDITIONAL_IGNORE.
   * @param augs Additional information that may include infoset augmentations.
   * @throws XNIException Thrown by handler to signal an error.
   * @see #CONDITIONAL_INCLUDE
   * @see #CONDITIONAL_IGNORE
   */
  public void startConditional(short type, Augmentations augs) throws XNIException {
  } // startConditional(short)

  /**
   * The end of a conditional section.
   *
   * @param augs Additional information that may include infoset augmentations.
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void endConditional(Augmentations augs) throws XNIException {
  } // endConditional()


  /**
   * The start of the DTD external subset.
   *
   * @param augs Additional information that may include infoset augmentations.
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void startExternalSubset(XMLResourceIdentifier identifier,
      Augmentations augs) throws XNIException {
    if (DEBUG_EVENTS) {
      System.out.println("==>startExternalSubset");
      if (DEBUG_BASEURI) {
        System.out.println("   expandedSystemId: " + identifier.getExpandedSystemId());
        System.out.println("   baseURI:" + identifier.getBaseSystemId());
      }
    }
    fBaseURIStack.push(identifier.getBaseSystemId());
    fInDTDExternalSubset = true;
  } // startExternalSubset(Augmentations)

  /**
   * The end of the DTD external subset.
   *
   * @param augs Additional information that may include infoset augmentations.
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void endExternalSubset(Augmentations augs) throws XNIException {
    fInDTDExternalSubset = false;
    fBaseURIStack.pop();
  } // endExternalSubset(Augmentations)

  /**
   * An internal entity declaration.
   *
   * @param name The name of the entity. Parameter entity names start with '%', whereas the name of
   * a general entity is just the entity name.
   * @param text The value of the entity.
   * @param nonNormalizedText The non-normalized value of the entity. This value contains the same
   * sequence of characters that was in the internal entity declaration, without any entity
   * references expanded.
   * @param augs Additional information that may include infoset augmentations.
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void internalEntityDecl(String name, XMLString text,
      XMLString nonNormalizedText,
      Augmentations augs) throws XNIException {

    if (DEBUG_EVENTS) {
      System.out.println("==>internalEntityDecl: " + name);
      if (DEBUG_BASEURI) {
        System.out.println("   baseURI:" + (String) fBaseURIStack.peek());
      }
    }
    // internal subset string
    if (fInternalSubset != null && !fInDTDExternalSubset) {
      fInternalSubset.append("<!ENTITY ");
      if (name.startsWith("%")) {
        fInternalSubset.append("% ");
        fInternalSubset.append(name.substring(1));
      } else {
        fInternalSubset.append(name);
      }
      fInternalSubset.append(' ');
      String value = nonNormalizedText.toString();
      boolean singleQuote = value.indexOf('\'') == -1;
      fInternalSubset.append(singleQuote ? '\'' : '"');
      fInternalSubset.append(value);
      fInternalSubset.append(singleQuote ? '\'' : '"');
      fInternalSubset.append(">\n");
    }

    // NOTE: We only know how to create these nodes for the Xerces
    //       DOM implementation because DOM Level 2 does not specify
    //       that functionality. -Ac

    // create full node
    // don't add parameter entities!
    if (name.startsWith("%")) {
      return;
    }
    if (fDocumentType != null) {
      NamedNodeMap entities = fDocumentType.getEntities();
      EntityImpl entity = (EntityImpl) entities.getNamedItem(name);
      if (entity == null) {
        entity = (EntityImpl) fDocumentImpl.createEntity(name);
        entity.setBaseURI((String) fBaseURIStack.peek());
        entities.setNamedItem(entity);
      }
    }

    // create deferred node
    if (fDocumentTypeIndex != -1) {
      boolean found = false;
      int node = fDeferredDocumentImpl.getLastChild(fDocumentTypeIndex, false);
      while (node != -1) {
        short nodeType = fDeferredDocumentImpl.getNodeType(node, false);
        if (nodeType == Node.ENTITY_NODE) {
          String nodeName = fDeferredDocumentImpl.getNodeName(node, false);
          if (nodeName.equals(name)) {
            found = true;
            break;
          }
        }
        node = fDeferredDocumentImpl.getRealPrevSibling(node, false);
      }
      if (!found) {
        int entityIndex =
            fDeferredDocumentImpl
                .createDeferredEntity(name, null, null, null, (String) fBaseURIStack.peek());
        fDeferredDocumentImpl.appendChild(fDocumentTypeIndex, entityIndex);
      }
    }

  } // internalEntityDecl(String,XMLString,XMLString)

  /**
   * An external entity declaration.
   *
   * @param name The name of the entity. Parameter entity names start with '%', whereas the name of
   * a general entity is just the entity name.
   * @param identifier An object containing all location information pertinent to this notation.
   * @param augs Additional information that may include infoset augmentations.
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void externalEntityDecl(String name, XMLResourceIdentifier identifier,
      Augmentations augs) throws XNIException {

    if (DEBUG_EVENTS) {
      System.out.println("==>externalEntityDecl: " + name);
      if (DEBUG_BASEURI) {
        System.out.println("   expandedSystemId:" + identifier.getExpandedSystemId());
        System.out.println("   baseURI:" + identifier.getBaseSystemId());
      }
    }
    // internal subset string
    String publicId = identifier.getPublicId();
    String literalSystemId = identifier.getLiteralSystemId();
    if (fInternalSubset != null && !fInDTDExternalSubset) {
      fInternalSubset.append("<!ENTITY ");
      if (name.startsWith("%")) {
        fInternalSubset.append("% ");
        fInternalSubset.append(name.substring(1));
      } else {
        fInternalSubset.append(name);
      }
      fInternalSubset.append(' ');
      if (publicId != null) {
        fInternalSubset.append("PUBLIC '");
        fInternalSubset.append(publicId);
        fInternalSubset.append("' '");
      } else {
        fInternalSubset.append("SYSTEM '");
      }
      fInternalSubset.append(literalSystemId);
      fInternalSubset.append("'>\n");
    }

    // NOTE: We only know how to create these nodes for the Xerces
    //       DOM implementation because DOM Level 2 does not specify
    //       that functionality. -Ac

    // create full node
    // don't add parameter entities!
    if (name.startsWith("%")) {
      return;
    }
    if (fDocumentType != null) {
      NamedNodeMap entities = fDocumentType.getEntities();
      EntityImpl entity = (EntityImpl) entities.getNamedItem(name);
      if (entity == null) {
        entity = (EntityImpl) fDocumentImpl.createEntity(name);
        entity.setPublicId(publicId);
        entity.setSystemId(literalSystemId);
        entity.setBaseURI(identifier.getBaseSystemId());
        entities.setNamedItem(entity);
      }
    }

    // create deferred node
    if (fDocumentTypeIndex != -1) {
      boolean found = false;
      int nodeIndex = fDeferredDocumentImpl.getLastChild(fDocumentTypeIndex, false);
      while (nodeIndex != -1) {
        short nodeType = fDeferredDocumentImpl.getNodeType(nodeIndex, false);
        if (nodeType == Node.ENTITY_NODE) {
          String nodeName = fDeferredDocumentImpl.getNodeName(nodeIndex, false);
          if (nodeName.equals(name)) {
            found = true;
            break;
          }
        }
        nodeIndex = fDeferredDocumentImpl.getRealPrevSibling(nodeIndex, false);
      }
      if (!found) {
        int entityIndex = fDeferredDocumentImpl.createDeferredEntity(
            name, publicId, literalSystemId, null, identifier.getBaseSystemId());
        fDeferredDocumentImpl.appendChild(fDocumentTypeIndex, entityIndex);
      }
    }

  } // externalEntityDecl(String,XMLResourceIdentifier, Augmentations)


  /**
   * This method notifies of the start of a parameter entity. The parameter
   * entity name start with a '%' character.
   *
   * @param name The name of the parameter entity.
   * @param identifier The resource identifier.
   * @param encoding The auto-detected IANA encoding name of the entity stream. This value will be
   * null in those situations where the entity encoding is not auto-detected (e.g. internal
   * parameter entities).
   * @param augs Additional information that may include infoset augmentations.
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void startParameterEntity(String name,
      XMLResourceIdentifier identifier,
      String encoding,
      Augmentations augs) throws XNIException {
    if (DEBUG_EVENTS) {
      System.out.println("==>startParameterEntity: " + name);
      if (DEBUG_BASEURI) {
        System.out.println("   expandedSystemId: " + identifier.getExpandedSystemId());
        System.out.println("   baseURI:" + identifier.getBaseSystemId());
      }
    }
    if (augs != null && fInternalSubset != null &&
        !fInDTDExternalSubset &&
        Boolean.TRUE.equals(augs.getItem(Constants.ENTITY_SKIPPED))) {
      fInternalSubset.append(name).append(";\n");
    }
    fBaseURIStack.push(identifier.getExpandedSystemId());
  }


  /**
   * This method notifies the end of a parameter entity. Parameter entity
   * names begin with a '%' character.
   *
   * @param name The name of the parameter entity.
   * @param augs Additional information that may include infoset augmentations.
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void endParameterEntity(String name, Augmentations augs) throws XNIException {

    if (DEBUG_EVENTS) {
      System.out.println("==>endParameterEntity: " + name);
    }
    fBaseURIStack.pop();
  }

  /**
   * An unparsed entity declaration.
   *
   * @param name The name of the entity.
   * @param identifier An object containing all location information pertinent to this entity.
   * @param notation The name of the notation.
   * @param augs Additional information that may include infoset augmentations.
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void unparsedEntityDecl(String name, XMLResourceIdentifier identifier,
      String notation, Augmentations augs)
      throws XNIException {

    if (DEBUG_EVENTS) {
      System.out.println("==>unparsedEntityDecl: " + name);
      if (DEBUG_BASEURI) {
        System.out.println("   expandedSystemId:" + identifier.getExpandedSystemId());
        System.out.println("   baseURI:" + identifier.getBaseSystemId());
      }
    }
    // internal subset string
    String publicId = identifier.getPublicId();
    String literalSystemId = identifier.getLiteralSystemId();
    if (fInternalSubset != null && !fInDTDExternalSubset) {
      fInternalSubset.append("<!ENTITY ");
      fInternalSubset.append(name);
      fInternalSubset.append(' ');
      if (publicId != null) {
        fInternalSubset.append("PUBLIC '");
        fInternalSubset.append(publicId);
        if (literalSystemId != null) {
          fInternalSubset.append("' '");
          fInternalSubset.append(literalSystemId);
        }
      } else {
        fInternalSubset.append("SYSTEM '");
        fInternalSubset.append(literalSystemId);
      }
      fInternalSubset.append("' NDATA ");
      fInternalSubset.append(notation);
      fInternalSubset.append(">\n");
    }

    // NOTE: We only know how to create these nodes for the Xerces
    //       DOM implementation because DOM Level 2 does not specify
    //       that functionality. -Ac

    // create full node
    if (fDocumentType != null) {
      NamedNodeMap entities = fDocumentType.getEntities();
      EntityImpl entity = (EntityImpl) entities.getNamedItem(name);
      if (entity == null) {
        entity = (EntityImpl) fDocumentImpl.createEntity(name);
        entity.setPublicId(publicId);
        entity.setSystemId(literalSystemId);
        entity.setNotationName(notation);
        entity.setBaseURI(identifier.getBaseSystemId());
        entities.setNamedItem(entity);
      }
    }

    // create deferred node
    if (fDocumentTypeIndex != -1) {
      boolean found = false;
      int nodeIndex = fDeferredDocumentImpl.getLastChild(fDocumentTypeIndex, false);
      while (nodeIndex != -1) {
        short nodeType = fDeferredDocumentImpl.getNodeType(nodeIndex, false);
        if (nodeType == Node.ENTITY_NODE) {
          String nodeName = fDeferredDocumentImpl.getNodeName(nodeIndex, false);
          if (nodeName.equals(name)) {
            found = true;
            break;
          }
        }
        nodeIndex = fDeferredDocumentImpl.getRealPrevSibling(nodeIndex, false);
      }
      if (!found) {
        int entityIndex = fDeferredDocumentImpl.createDeferredEntity(
            name, publicId, literalSystemId, notation, identifier.getBaseSystemId());
        fDeferredDocumentImpl.appendChild(fDocumentTypeIndex, entityIndex);
      }
    }

  } // unparsedEntityDecl(String,XMLResourceIdentifier, String, Augmentations)

  /**
   * A notation declaration
   *
   * @param name The name of the notation.
   * @param identifier An object containing all location information pertinent to this notation.
   * @param augs Additional information that may include infoset augmentations.
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void notationDecl(String name, XMLResourceIdentifier identifier,
      Augmentations augs) throws XNIException {

    // internal subset string
    String publicId = identifier.getPublicId();
    String literalSystemId = identifier.getLiteralSystemId();
    if (fInternalSubset != null && !fInDTDExternalSubset) {
      fInternalSubset.append("<!NOTATION ");
      fInternalSubset.append(name);
      if (publicId != null) {
        fInternalSubset.append(" PUBLIC '");
        fInternalSubset.append(publicId);
        if (literalSystemId != null) {
          fInternalSubset.append("' '");
          fInternalSubset.append(literalSystemId);
        }
      } else {
        fInternalSubset.append(" SYSTEM '");
        fInternalSubset.append(literalSystemId);
      }
      fInternalSubset.append("'>\n");
    }

    // NOTE: We only know how to create these nodes for the Xerces
    //       DOM implementation because DOM Level 2 does not specify
    //       that functionality. -Ac

    // create full node
    if (fDocumentImpl != null && fDocumentType != null) {
      NamedNodeMap notations = fDocumentType.getNotations();
      if (notations.getNamedItem(name) == null) {
        NotationImpl notation = (NotationImpl) fDocumentImpl.createNotation(name);
        notation.setPublicId(publicId);
        notation.setSystemId(literalSystemId);
        notation.setBaseURI(identifier.getBaseSystemId());
        notations.setNamedItem(notation);
      }
    }

    // create deferred node
    if (fDocumentTypeIndex != -1) {
      boolean found = false;
      int nodeIndex = fDeferredDocumentImpl.getLastChild(fDocumentTypeIndex, false);
      while (nodeIndex != -1) {
        short nodeType = fDeferredDocumentImpl.getNodeType(nodeIndex, false);
        if (nodeType == Node.NOTATION_NODE) {
          String nodeName = fDeferredDocumentImpl.getNodeName(nodeIndex, false);
          if (nodeName.equals(name)) {
            found = true;
            break;
          }
        }
        nodeIndex = fDeferredDocumentImpl.getPrevSibling(nodeIndex, false);
      }
      if (!found) {
        int notationIndex = fDeferredDocumentImpl.createDeferredNotation(
            name, publicId, literalSystemId, identifier.getBaseSystemId());
        fDeferredDocumentImpl.appendChild(fDocumentTypeIndex, notationIndex);
      }
    }

  } // notationDecl(String,XMLResourceIdentifier, Augmentations)

  /**
   * Characters within an IGNORE conditional section.
   *
   * @param text The ignored text.
   * @param augs Additional information that may include infoset augmentations.
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void ignoredCharacters(XMLString text, Augmentations augs) throws XNIException {
  } // ignoredCharacters(XMLString, Augmentations)


  /**
   * An element declaration.
   *
   * @param name The name of the element.
   * @param contentModel The element content model.
   * @param augs Additional information that may include infoset augmentations.
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void elementDecl(String name, String contentModel, Augmentations augs)
      throws XNIException {

    // internal subset string
    if (fInternalSubset != null && !fInDTDExternalSubset) {
      fInternalSubset.append("<!ELEMENT ");
      fInternalSubset.append(name);
      fInternalSubset.append(' ');
      fInternalSubset.append(contentModel);
      fInternalSubset.append(">\n");
    }

  } // elementDecl(String,String)

  /**
   * An attribute declaration.
   *
   * @param elementName The name of the element that this attribute is associated with.
   * @param attributeName The name of the attribute.
   * @param type The attribute type. This value will be one of the following: "CDATA", "ENTITY",
   * "ENTITIES", "ENUMERATION", "ID", "IDREF", "IDREFS", "NMTOKEN", "NMTOKENS", or "NOTATION".
   * @param enumeration If the type has the value "ENUMERATION" or "NOTATION", this array holds the
   * allowed attribute values; otherwise, this array is null.
   * @param defaultType The attribute default type. This value will be one of the following:
   * "#FIXED", "#IMPLIED", "#REQUIRED", or null.
   * @param defaultValue The attribute default value, or null if no default value is specified.
   * @param nonNormalizedDefaultValue The attribute default value with no normalization performed,
   * or null if no default value is specified.
   * @param augs Additional information that may include infoset augmentations.
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void attributeDecl(String elementName, String attributeName,
      String type, String[] enumeration,
      String defaultType, XMLString defaultValue,
      XMLString nonNormalizedDefaultValue, Augmentations augs) throws XNIException {

    // internal subset string
    if (fInternalSubset != null && !fInDTDExternalSubset) {
      fInternalSubset.append("<!ATTLIST ");
      fInternalSubset.append(elementName);
      fInternalSubset.append(' ');
      fInternalSubset.append(attributeName);
      fInternalSubset.append(' ');
      if (type.equals("ENUMERATION")) {
        fInternalSubset.append('(');
        for (int i = 0; i < enumeration.length; i++) {
          if (i > 0) {
            fInternalSubset.append('|');
          }
          fInternalSubset.append(enumeration[i]);
        }
        fInternalSubset.append(')');
      } else {
        fInternalSubset.append(type);
      }
      if (defaultType != null) {
        fInternalSubset.append(' ');
        fInternalSubset.append(defaultType);
      }
      if (defaultValue != null) {
        fInternalSubset.append(" '");
        for (int i = 0; i < defaultValue.length; i++) {
          char c = defaultValue.ch[defaultValue.offset + i];
          if (c == '\'') {
            fInternalSubset.append("&apos;");
          } else {
            fInternalSubset.append(c);
          }
        }
        fInternalSubset.append('\'');
      }
      fInternalSubset.append(">\n");
    }
    // REVISIT: This code applies to the support of domx/grammar-access
    // feature in Xerces 1

    // deferred expansion
    if (fDeferredDocumentImpl != null) {

      // get the default value
      if (defaultValue != null) {

        // get element definition
        int elementDefIndex = fDeferredDocumentImpl.lookupElementDefinition(elementName);

        // create element definition if not already there
        if (elementDefIndex == -1) {
          elementDefIndex = fDeferredDocumentImpl.createDeferredElementDefinition(elementName);
          fDeferredDocumentImpl.appendChild(fDocumentTypeIndex, elementDefIndex);
        }
        // add default attribute
        boolean nsEnabled = fNamespaceAware;
        String namespaceURI = null;
        if (nsEnabled) {
          // DOM Level 2 wants all namespace declaration attributes
          // to be bound to "http://www.w3.org/2000/xmlns/"
          // So as long as the XML parser doesn't do it, it needs to
          // done here.
          if (attributeName.startsWith("xmlns:") ||
              attributeName.equals("xmlns")) {
            namespaceURI = NamespaceContext.XMLNS_URI;
          } else if (attributeName.startsWith("xml:")) {
            namespaceURI = NamespaceContext.XML_URI;
          }
        }
        int attrIndex = fDeferredDocumentImpl.createDeferredAttribute(
            attributeName, namespaceURI, defaultValue.toString(), false);
        if ("ID".equals(type)) {
          fDeferredDocumentImpl.setIdAttribute(attrIndex);
        }
        // REVISIT: set ID type correctly
        fDeferredDocumentImpl.appendChild(elementDefIndex, attrIndex);
      }

    } // if deferred

    // full expansion
    else if (fDocumentImpl != null) {

      // get the default value
      if (defaultValue != null) {

        // get element definition node
        NamedNodeMap elements = ((DocumentTypeImpl) fDocumentType).getElements();
        ElementDefinitionImpl elementDef = (ElementDefinitionImpl) elements
            .getNamedItem(elementName);
        if (elementDef == null) {
          elementDef = fDocumentImpl.createElementDefinition(elementName);
          ((DocumentTypeImpl) fDocumentType).getElements().setNamedItem(elementDef);
        }

        // REVISIT: Check for uniqueness of element name? -Ac

        // create attribute and set properties
        boolean nsEnabled = fNamespaceAware;
        AttrImpl attr;
        if (nsEnabled) {
          String namespaceURI = null;
          // DOM Level 2 wants all namespace declaration attributes
          // to be bound to "http://www.w3.org/2000/xmlns/"
          // So as long as the XML parser doesn't do it, it needs to
          // done here.
          if (attributeName.startsWith("xmlns:") ||
              attributeName.equals("xmlns")) {
            namespaceURI = NamespaceContext.XMLNS_URI;
          } else if (attributeName.startsWith("xml:")) {
            namespaceURI = NamespaceContext.XML_URI;
          }
          attr = (AttrImpl) fDocumentImpl.createAttributeNS(namespaceURI,
              attributeName);
        } else {
          attr = (AttrImpl) fDocumentImpl.createAttribute(attributeName);
        }
        attr.setValue(defaultValue.toString());
        attr.setSpecified(false);
        attr.setIdAttribute("ID".equals(type));

        // add default attribute to element definition
        if (nsEnabled) {
          elementDef.getAttributes().setNamedItemNS(attr);
        } else {
          elementDef.getAttributes().setNamedItem(attr);
        }
      }

    } // if NOT defer-node-expansion

  } // attributeDecl(String,String,String,String[],String,XMLString, XMLString, Augmentations)


  /**
   * The start of an attribute list.
   *
   * @param elementName The name of the element that this attribute list is associated with.
   * @param augs Additional information that may include infoset augmentations.
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void startAttlist(String elementName, Augmentations augs) throws XNIException {
  } // startAttlist(String)


  /**
   * The end of an attribute list.
   *
   * @param augs Additional information that may include infoset augmentations.
   * @throws XNIException Thrown by handler to signal an error.
   */
  public void endAttlist(Augmentations augs) throws XNIException {
  } // endAttlist()


  // method to create an element node.
  // subclasses can override this method to create element nodes in other ways.
  protected Element createElementNode(QName element) {
    Element el = null;

    if (fNamespaceAware) {
      // if we are using xerces DOM implementation, call our
      // own constructor to reuse the strings we have here.
      if (fDocumentImpl != null) {
        el = fDocumentImpl.createElementNS(element.uri, element.rawname,
            element.localpart);
      } else {
        el = fDocument.createElementNS(element.uri, element.rawname);
      }
    } else {
      el = fDocument.createElement(element.rawname);
    }

    return el;
  }

  // method to create an attribute node.
  // subclasses can override this method to create attribute nodes in other ways.
  protected Attr createAttrNode(QName attrQName) {
    Attr attr = null;

    if (fNamespaceAware) {
      if (fDocumentImpl != null) {
        // if we are using xerces DOM implementation, call our
        // own constructor to reuse the strings we have here.
        attr = fDocumentImpl.createAttributeNS(attrQName.uri,
            attrQName.rawname,
            attrQName.localpart);
      } else {
        attr = fDocument.createAttributeNS(attrQName.uri,
            attrQName.rawname);
      }
    } else {
      attr = fDocument.createAttribute(attrQName.rawname);
    }

    return attr;
  }

  /*
     * When the first characters() call is received, the data is stored in
     * a new Text node. If right after the first characters() we receive another chunk of data,
     * the data from the Text node, following the new characters are appended
     * to the fStringBuffer and the text node data is set to empty.
     *
     * This function is called when the state is changed and the
     * data must be appended to the current node.
     *
     * Note: if DOMFilter is set, you must make sure that if Node is skipped,
     * or removed fFistChunk must be set to true, otherwise some data can be lost.
     *
     */
  protected void setCharacterData(boolean sawChars) {

    // handle character data
    fFirstChunk = sawChars;

    // if we have data in the buffer we must have created
    // a text node already.

    Node child = fCurrentNode.getLastChild();
    if (child != null) {
      if (fStringBuilder.length() > 0) {
        // REVISIT: should this check be performed?
        if (child.getNodeType() == Node.TEXT_NODE) {
          if (fDocumentImpl != null) {
            ((TextImpl) child).replaceData(fStringBuilder.toString());
          } else {
            ((Text) child).setData(fStringBuilder.toString());
          }
        }
        // reset string buffer
        fStringBuilder.setLength(0);
      }

      if (fDOMFilter != null && !fInEntityRef) {
        if ((child.getNodeType() == Node.TEXT_NODE) &&
            ((fDOMFilter.getWhatToShow() & NodeFilter.SHOW_TEXT) != 0)) {
          short code = fDOMFilter.acceptNode(child);
          switch (code) {
            case LSParserFilter.FILTER_INTERRUPT: {
              throw Abort.INSTANCE;
            }
            case LSParserFilter.FILTER_REJECT: {
              // fall through to SKIP since Comment has no children.
            }
            case LSParserFilter.FILTER_SKIP: {
              fCurrentNode.removeChild(child);
              return;
            }
            default: {
              // accept node -- do nothing
            }
          }
        }
      }   // end-if fDOMFilter !=null

    } // end-if child !=null
  }


  /**
   * @see org.w3c.dom.ls.LSParser#abort()
   */
  public void abort() {
    throw Abort.INSTANCE;
  }


} // class AbstractDOMParser
